package com.anlogic.sdk.bsp.handlers;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.Logger;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.extension.CConfigurationData;
import org.eclipse.cdt.core.templateengine.process.ProcessFailureException;
import org.eclipse.cdt.managedbuilder.buildproperties.IBuildProperty;
import org.eclipse.cdt.managedbuilder.core.BuildException;
import org.eclipse.cdt.managedbuilder.core.IBuilder;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.internal.core.Configuration;
import org.eclipse.cdt.managedbuilder.internal.core.ManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.internal.core.ManagedProject;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.swt.widgets.Display;

import com.anlogic.sdk.bsp.Constants;
import com.anlogic.sdk.bsp.model.IPlatformProject;
import com.anlogic.sdk.bsp.model.PlatformModel;
import com.anlogic.sdk.bsp.model.PlatformProject;
import com.anlogic.sdk.bsp.model.PlatformProjectNature;
import com.anlogic.sdk.project.settings.ProjectSettings;
import com.anlogic.sdk.toolchain.ArmToolchain;
import com.anlogic.sdk.toolchain.RiscvToolchain;
import com.anlogic.sdk.tools.IBspTool;
import com.anlogic.sdk.tools.utils.ToolsUtils;
import com.anlogic.sdk.utils.EnvUtils;
import com.anlogic.sdk.utils.LogUtils;
import com.anlogic.sdk.utils.ProjectUtils;
import com.anlogic.sdk.utils.options.BitWidth;
import com.anlogic.sdk.utils.options.ChipType;
import com.anlogic.sdk.utils.toolchain.ToolchainUtils;

@SuppressWarnings("restriction")
public class PlatformProjectCreator {
	private static Logger logger = LogUtils.getLogger(PlatformProjectCreator.class);

	public PlatformProjectCreator() {
	}

	public IPlatformProject createPlatformProject(PlatformModel model, IProgressMonitor monitor) {
		IProject project = null;
		SubMonitor subMonitor = SubMonitor.convert(monitor, 15);
		try {
			logger.info("Create Platform Project, run createCDTProject");
			project = createCDTProject(model.getProjName(), model.getProjLoc(), subMonitor);
		} catch (CoreException e) {
			logger.error("Create Platform Project Error while running createCDTProject {}", e.getMessage());
		}
		logger.info("Create Platform Project, run createPlatformProject");
		IPlatformProject platformPrj = PlatformProject.createPlatformProject(project, model.getPlatformFile(), model.getChip(), model.getProc(), model.getOs());
		logger.info("Create Platform Project, run preProcessor");
		preProcessor(project, model.getOs(), model.getChip());
		logger.info("Create Platform Project, run generateBsp");
		generateBsp(subMonitor.split(10), platformPrj, model.getPlatformFile(), model.getChip(), model.getProc(), model.getOs());
		logger.info("Create Platform Project done");
		return platformPrj;
	}

	private IProject createCDTProject(String projectName, String location, IProgressMonitor monitor) throws CoreException {
		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);

		IProjectDescription description = ResourcesPlugin.getWorkspace().newProjectDescription(projectName);
		String[] natures = new String[1];
		natures[0] = PlatformProjectNature.getId();
		description.setNatureIds(natures);

		IPath projectLocation = new Path(location);
		if (location != null && !location.trim().isEmpty()) {
			description.setLocation(projectLocation);
		}

		CCorePlugin.getDefault().createCDTProject(description, project, monitor);

		if (!project.isOpen()) {
			project.open(monitor);
		}

		return project;
	}
	
	private void preProcessor(IProject project, String os, String chip) {
		try {
			setConfigurations(project, getConfigurations(chip));
			setProjectSettings(project, os, chip);
			setProjectToolchain(project, os, chip);
			
			String projectType = Constants.getProjectType(chip);
			String arch = ChipType.getArch(chip, BitWidth.BIT64.getName()).getName();
			String toolchainPath = ToolchainUtils.getToolchainPath(os, arch, projectType);
			String toolchainPrefix = ToolchainUtils.getToolchainPrefix(os, arch, projectType);
			
			Map<String, String> vars = new HashMap<String, String> ();
			vars.put("COMPILE_PREFIX", toolchainPath + "/" + toolchainPrefix);
			ProjectUtils.setBuildVariables(project, vars);
			ManagedBuildManager.saveBuildInfo(project, true);
		} catch (CoreException e) {
			logger.error("Create Platform Project Error while setting application project configurations {}", e.getMessage());
		}
	}

	private void setConfigurations(IProject project, List<IConfiguration> configs)
			throws CoreException {
		String PROPERTY = "org.eclipse.cdt.build.core.buildType";
		String PROP_VAL = PROPERTY + ".debug";
		String artifactExtension = "lib";

		CoreModel coreModel = CoreModel.getDefault();
		ICProjectDescription des = coreModel.createProjectDescription(project, false);
		ManagedBuildInfo info = ManagedBuildManager.createBuildInfo(project);
		ManagedProject newManagedProject = new ManagedProject(project, configs.get(0).getProjectType());
		info.setManagedProject(newManagedProject);

		Map<IConfiguration, IConfiguration> original2newConfigs = new HashMap<>();
		ICConfigurationDescription active = null;
		for (IConfiguration cfg : configs) {
			if (cfg != null) {
				String id = ManagedBuildManager.calculateChildId(cfg.getId(), null);
				Configuration configuration = new Configuration(newManagedProject, (Configuration) cfg, id, false,
						true);
				CConfigurationData data = configuration.getConfigurationData();
				ICConfigurationDescription cfgDes = des.createConfiguration(ManagedBuildManager.CFG_DATA_PROVIDER_ID,
						data);
				configuration.setConfigurationDescription(cfgDes);
				configuration.exportArtifactInfo();
				configuration.setArtifactExtension(artifactExtension);
				original2newConfigs.put(cfg, configuration);

				IBuilder builder = configuration.getEditableBuilder();
				if (builder != null) {
					builder.setManagedBuildOn(false);
					builder.setBuildPath("${workspace_loc:${ProjName}}");
					builder.setAutoBuildEnable(false);
					builder.setIncrementalBuildEnable(false);
					builder.setCleanBuildEnable(false);
				}

				configuration.setName(cfg.getName());
				configuration.setArtifactName(newManagedProject.getDefaultArtifactName());

				IBuildProperty buildProperty = configuration.getBuildProperties().getProperty(PROPERTY);
				if (buildProperty != null && buildProperty.getValue() != null
						&& PROP_VAL.equals(buildProperty.getValue().getId())) {
					active = cfgDes;
				} else if (active == null) {
					active = cfgDes;
				}
			}
		}

		if (active != null) {
			active.setActive();
		}
		coreModel.setProjectDescription(project, des);

		info.setValid(true);
		ManagedBuildManager.saveBuildInfo(project, true);
	}

	private List<IConfiguration> getConfigurations(String chipName) {
		ArrayList<IConfiguration> configs = new ArrayList<IConfiguration>();

		if (ChipType.isArm(chipName)) {
			configs.add(ManagedBuildManager
					.getExtensionConfiguration("ilg.gnuarmeclipse.managedbuild.cross.config.lib.debug"));
			configs.add(ManagedBuildManager
					.getExtensionConfiguration("ilg.gnuarmeclipse.managedbuild.cross.config.lib.release"));
		} else if (ChipType.isRiscv(chipName)) {
			configs.add(ManagedBuildManager
					.getExtensionConfiguration("ilg.gnumcueclipse.managedbuild.cross.riscv.config.lib.debug"));
			configs.add(ManagedBuildManager
					.getExtensionConfiguration("ilg.gnumcueclipse.managedbuild.cross.riscv.config.lib.release"));
		}
		return configs;
	}
	
	private void setIncludePath(IProject project, String chipName) {
		IBspTool bspTool = ToolsUtils.getBspTool(chipName);
		
		String projectName = project.getName();
		String projLoc = project.getLocation().toString();
		
		List<String> projIncludes = bspTool.getIncludePaths(projLoc);
		String[] includePaths = new String[projIncludes.size()];
		for (int i = 0; i < projIncludes.size(); i++) {
			includePaths[i] = String.format("\"%s\"", projIncludes.get(i).replace(projLoc, "${ProjDirPath}"));
		}
		
		if (ChipType.isArm(chipName)) {
			for (IConfiguration config : ManagedBuildManager.getBuildInfo((IResource) project).getManagedProject().getConfigurations()) {
				try {
					new com.anlogic.sdk.project.settings.utils.arm.SetStringListOptionByID(projectName, config.getName()).setCompilerIncludePaths(includePaths);
				} catch (BuildException | ProcessFailureException e) {
					logger.error("Set project include paths failed!");
				}
			}
		} else {
			for (IConfiguration config : ManagedBuildManager.getBuildInfo((IResource) project).getManagedProject().getConfigurations()) {
				try {
					new com.anlogic.sdk.project.settings.utils.riscv.SetStringListOptionByID(projectName, config.getName()).setCompilerIncludePaths(includePaths);
				} catch (BuildException | ProcessFailureException e) {
					logger.error("Set project include paths failed!");
				}
			}
		}
		ManagedBuildManager.saveBuildInfo(project, true);
	}

	private void setProjectSettings(IProject project, String os, String chipName) {
		ProjectSettings projSettings = new ProjectSettings(project);
		projSettings.createNewSettings();
		projSettings.setProjectType(Constants.getProjectType(chipName));
		projSettings.setArch(ChipType.getArch(chipName, BitWidth.BIT64.getName()).getName());
		projSettings.setOs(os);
		projSettings.saveProjectSettings();
	}

	private void setProjectToolchain(IProject project, String os, String chipName) {
		String projectType = Constants.getProjectType(chipName);
		String arch = ChipType.getArch(chipName, BitWidth.BIT64.getName()).getName();

		String toolchainName = ToolchainUtils.getToolchainName(os, arch, projectType);
		String toolchainPath = ToolchainUtils.getToolchainPath(os, arch, projectType);
		String buildToolsPath = ToolchainUtils.getBuildToolsPath();

		if (ChipType.isArm(chipName)) {
			ArmToolchain.setToolchain(project, toolchainName, toolchainPath, buildToolsPath);
		} else if (ChipType.isRiscv(chipName)) {
			RiscvToolchain.setToolchain(project, toolchainName, toolchainPath, buildToolsPath);
		}
	}

	private void postProcessor(IPlatformProject platformProj) {
		String filePath = platformProj.getLoc() + "/tools/make/rules.mk";
		EnvUtils.addIDEVersionToMakefile(filePath);
		setIncludePath(platformProj.getIProject(), platformProj.getChip());
	}
	
	public void generateBsp(IProgressMonitor monitor, IPlatformProject platformProj, String hpfPath, String chip, String processor, String os) {
		Display.getDefault().asyncExec(new Runnable() {
			@Override
			public void run() {
				IProject project = platformProj.getIProject();
				IBspTool bspTool = ToolsUtils.getBspTool(chip);
				bspTool.createMssFromHpf(hpfPath, platformProj.getMssPath(), chip, processor, os);
				bspTool.generteBsp(hpfPath, platformProj.getMssPath(), project.getLocation().toString());
				postProcessor(platformProj);
				try {
					project.refreshLocal(2, monitor);
				} catch (CoreException e) {
					logger.error("Create Platform Project Error while refreshing newly created platform project {}", e.getMessage());
				}
			}
		});
	}

	public void deleteProject(String projName) {
		try {
			IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName);
			if (project.exists()) {
				project.delete(true, null);
			}
		} catch (CoreException e) {
			logger.error("Create Platform Project Error while deleting newly created platform project");
		}
	}
	
	public void refreshProject(String projName) {
		try {
			IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName);
			if (project.exists()) {
				project.refreshLocal(IResource.DEPTH_INFINITE, null);
			}
		} catch (CoreException e) {
			logger.error("Create Platform Project Error while refreshing platform project {}", e.getMessage());
		}
	}
}
